package com.netty.demo.server.utils;

import com.netty.demo.server.constant.ConstantValue;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Dom4j解析xml文件
 *
 * @author Administrator 余秀良
 * @version 1.0
 * <p>date: 2019年09月17日  09:20 </p>
 * <p>address: 四川成都 </p>
 */
@Slf4j
public final class ParserXmlUtil {
    /**
     * 读取xml文件转为java实体类
     *
     * @param xmlPath xml文件地址
     * @param clazz   需要转换的类
     * @return {@link List<T>}
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 19  14:47    </p>
     * <p>  address: 四川成都  </p>
     */
    public static <T> List<T> dom4jXmlToBean(String xmlPath, Class<T> clazz) {
        List<T> list = new LinkedList<>();
        try {
            //创建SAXReader的对象reader
            SAXReader reader = new SAXReader();
            //通过reader对象的read方法加载xml文件,获取 document 对象。
            File file = new File(xmlPath);
            //通过reader对象的read方法加载xml文件,获取 document 对象。
            Document document = reader.read(file);
            //通过document对象获取根节点
            Element root = document.getRootElement();
            //创建对象
            T t = clazz.getDeclaredConstructor().newInstance();
            //获取所有节点
            List<Element> elements = root.elements();
            elements.forEach(element -> {
                try {
                    //获取属性名(首字母大写)
                    String name = element.getName();
                    String text = element.getText();
                    Field declaredField = t.getClass().getDeclaredField(name.toLowerCase());
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(declaredField.getName(), t.getClass());
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    Class<?> type = declaredField.getType();
                    List<String> list1 = Collections.singletonList(text);
                    if (type.equals(Integer.class)) {
                        writeMethod.invoke(t, Integer.valueOf(text));
                    } else if (type.equals(List.class)) {
                        writeMethod.invoke(t, list1);
                    } else {
                        writeMethod.invoke(t, text);
                    }
                } catch (IllegalAccessException | InvocationTargetException | NoSuchFieldException | IntrospectionException e) {
                    log.error("Dom4jParserXmlUtil的get方法出现异常 {} {}", e.getMessage(), e.getLocalizedMessage());
                    e.printStackTrace();
                }
            });
            list.add(t);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 实体类转为xml文件
     *
     * @param xmlPath xml文件存放目录
     * @param clazz   需要转换的类
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 19  14:47    </p>
     * <p>  address: 四川成都  </p>
     */
    public static <T> void dom4jBeanToXml(String xmlPath, T clazz) {
        FileOutputStream fileOutputStream = null;
        XMLWriter xmlWriter = null;
        try {
            Document document = DocumentHelper.createDocument();
            try {
                Element rootElement = document.addElement(clazz.getClass().getSimpleName());
                //获取实体类的所有属性，返回Field数组
                Field[] declaredFields = clazz.getClass().getDeclaredFields();
                //遍历所有有属性
                for (Field declaredField : declaredFields) {
                    // 获取属属性的名字
                    String name = declaredField.getName();
                    //去除串行化序列属性
                    if (!name.equals(ConstantValue.SERIAL_VERSION_UID)) {
                        //将属性的首字符大写，方便构造get，set方法
                        name = name.substring(0, 1).toUpperCase() + name.substring(1);
                        Method m = clazz.getClass().getMethod("get" + name);
                        Class<?> type = declaredField.getType();
                        System.out.println(type);
                        //获取属性值
                        Object data = m.invoke(clazz);
                        if (data != null) {
                            Element element = rootElement.addElement(name);
                            element.setData(data);
                            element.setText(String.valueOf(data));
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            OutputFormat prettyPrint = OutputFormat.createPrettyPrint();
            fileOutputStream = new FileOutputStream(xmlPath);
            xmlWriter = new XMLWriter(fileOutputStream, prettyPrint);
            xmlWriter.write(document);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (xmlWriter != null) {
                    xmlWriter.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 使用 JAXB 生成xml文件
     * 缺点是 需要类和xml映射 字段需要对应
     *
     * @param xmlPath 文件存放路径
     * @param clazz   需要转为xml文件的实体对象
     * @return 返回 xml文件字符
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 17  16:10    </p>
     * <p>  address: 四川成都  </p>
     */
    public static <T> String jaxbBeanToXml(String xmlPath, T clazz) {
        OutputStreamWriter outputStreamWriter = null;
        BufferedWriter bufferedWriter = null;
        try {
            File file = new File(xmlPath);
            JAXBContext jaxbContext = JAXBContext.newInstance(clazz.getClass());
            Marshaller marshaller = jaxbContext.createMarshaller();
            //格式化输出
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            //编码格式
            marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
            //去掉默认报文头
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
            StringWriter writer = new StringWriter();
            //重写报文头
            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            writer.write("\n");
            marshaller.marshal(clazz, writer);
            String result = writer.toString();
            //写入到xml文件中
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            outputStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8);
            bufferedWriter = new BufferedWriter(outputStreamWriter);
            bufferedWriter.write(result);
            return result;
        } catch (JAXBException | IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bufferedWriter != null) {
                    bufferedWriter.close();
                }
                if (outputStreamWriter != null) {
                    outputStreamWriter.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 使用 JAXB 解析 xml文件
     * 缺点是 需要类和xml映射 字段需要对应
     *
     * @param xmlPath 文件存放路径
     * @param clazz   需要转为xml文件的实体对象
     * @return 返回 xml文件字符
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 17  16:10    </p>
     * <p>  address: 四川成都  </p>
     */
    public static <T> T jaxbXmlToBean(String xmlPath, Class<T> clazz) {
        try {
            File file = new File(xmlPath);
            JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            return clazz.cast(unmarshaller.unmarshal(file));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 读取xml文件转为 list
     *
     * @param xmlPath xml文件存放目录
     * @return 转换后的list
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 19  18:08    </p>
     * <p>  address: 四川成都  </p>
     */
    public static List<Map<String, Object>> dom4jXmlToList(String xmlPath) {
        List<Map<String, Object>> xmlNodeList = new LinkedList<>();
        try {
            File file = new File(xmlPath);
            if (file.exists()) {
                //创建SAXReader的对象reader
                SAXReader reader = new SAXReader();
                //通过reader对象的read方法加载xml文件,获取 document 对象。
                Document document = reader.read(file);
                Element rootElement = document.getRootElement();
                elementToList(xmlNodeList, rootElement);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
            return xmlNodeList;
        }
        return xmlNodeList;
    }

    /**
     * 读取xml文件转为 map
     * 该方法的缺点是 xml 文件的节点不能重复
     * 无法获取全部节点名称相同的数据
     * 因为 map 的键不能重复
     *
     * @param xmlPath xml文件存放目录
     * @return 转换后的 map
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 19  18:08    </p>
     * <p>  address: 四川成都  </p>
     */
    public static Map<String, Object> dom4jXmlToMap(String xmlPath) {
        Map<String, Object> xmlNodeMap = new LinkedHashMap<>(16);
        try {
            File file = new File(xmlPath);
            if (file.exists()) {
                //创建SAXReader的对象reader
                SAXReader reader = new SAXReader();
                //通过reader对象的read方法加载xml文件,获取 document 对象。
                Document document = reader.read(file);
                Element rootElement = document.getRootElement();
                elementToMap(xmlNodeMap, rootElement);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
            return xmlNodeMap;
        }
        return xmlNodeMap;
    }

    /**
     * 把节点转为 map
     * 为了递归调用，所以单独写了该方法
     *
     * @param xmlNodeMap  存放 数据的 map
     * @param rootElement 节点
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 19  18:09    </p>
     * <p>  address: 四川成都  </p>
     */
    private static void elementToMap(Map<String, Object> xmlNodeMap, Element rootElement) {
        // 获得当前节点的子节点
        List<Element> childElements = rootElement.elements();
        if (childElements.size() > 0) {
            Map<String, Object> map = new LinkedHashMap<>();
            for (Element e : childElements) {
                elementToMap(map, e);
                xmlNodeMap.put(rootElement.getName(), map);
                Map<String, Object> attributeMap = getElementAttributes(rootElement);
                if (attributeMap.size() > 0) {
                    xmlNodeMap.put("attribute", attributeMap);
                }
            }
        } else {
            Map<String, Object> attributeMap = getElementAttributes(rootElement);
            if (attributeMap.size() > 0) {
                xmlNodeMap.put("attribute", attributeMap);
            }
            xmlNodeMap.put(rootElement.getName(), rootElement.getText());
        }
    }

    /**
     * 把节点转为 list
     * 为了递归调用，所以单独写了该方法
     *
     * @param xmlNodeList 存放 数据的 list
     * @param rootElement 节点
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 19  18:09    </p>
     * <p>  address: 四川成都  </p>
     */
    private static void elementToList(List<Map<String, Object>> xmlNodeList, Element rootElement) {
        // 获得当前节点的子节点
        List<Element> childElements = rootElement.elements();
        if (childElements.size() > 0) {
            for (Element e : childElements) {
                elementToList(xmlNodeList, e);
                if (!rootElement.isRootElement()) {
                    addElementToList(xmlNodeList, rootElement);
                }
            }
        } else {
            addElementToList(xmlNodeList, rootElement);
        }
    }

    /**
     * 该方法把节点信息添加到 list
     *
     * @param xmlNodeList 存放数据的 list
     * @param rootElement 节点
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 19  18:11    </p>
     * <p>  address: 四川成都  </p>
     */
    private static void addElementToList(List<Map<String, Object>> xmlNodeList, Element rootElement) {
        LinkedHashMap<String, Object> objectObjectLinkedHashMap = new LinkedHashMap<>();
        Map<String, Object> attributeMap = getElementAttributes(rootElement);
        if (attributeMap.size() > 0) {
            objectObjectLinkedHashMap.put("attribute", attributeMap);
        }
        objectObjectLinkedHashMap.put(rootElement.getName().trim(), rootElement.getText().trim());
        xmlNodeList.add(objectObjectLinkedHashMap);
    }

    /**
     * 该方法获取节点中的 attribute 信息
     *
     * @param element 节点对象
     * @return 返回该节点的 attribute 信息
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 19  18:12    </p>
     * <p>  address: 四川成都  </p>
     */
    private static Map<String, Object> getElementAttributes(Element element) {
        List<Attribute> attributes = element.attributes();
        Map<String, Object> attributeMap = new LinkedHashMap<>();
        for (Attribute attribute : attributes) {
            attributeMap.put(attribute.getName().trim(), attribute.getValue().trim());
        }
        return attributeMap;
    }

    /**
     * 读取xml配置文件 获取根节点
     *
     * @param xmlPath xml 文件 路径
     * @return {@link Element}
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 19  19:46    </p>
     * <p>  address: 四川成都  </p>
     */
    public static Element getRootElement(String xmlPath) {
        try {
            File file = new File(xmlPath);
            if (file.exists()) {
                //创建SAXReader的对象reader
                SAXReader reader = new SAXReader();
                //通过reader对象的read方法加载xml文件,获取 document 对象。
                Document document = reader.read(file);
                return document.getRootElement();
            }
        } catch (DocumentException e) {
            e.printStackTrace();
            return null;
        }
        return null;
    }
}